//===================================================================================================================
//Preprocess Settings
#ifndef GI_DEPTH_MIPLEVELS
	#define GI_DEPTH_MIPLEVELS		11	//[>1] Mip levels to increase speed at the cost of quality
#endif
#ifndef GI_DEPTH_TEXTURE_QUALITY
	#define GI_DEPTH_TEXTURE_QUALITY		1.0	//[>0.0] 1.0 - Screen resolution. Lowering this might ruin the AO precision. Go from 1.0 to AO texture quality.
#endif
#ifndef GI_DIFFUSE_PASSES
	#define GI_DIFFUSE_PASSES		4	//[>1] Number of passes in the shader.
#endif
#ifndef GI_TEXTURE_QUALITY
	#define GI_TEXTURE_QUALITY 		1.0	//[>0.0] 1.0 - Screen resolution.
#endif
#ifndef GI_VECTOR_MODE
	#define GI_VECTOR_MODE			2	//[0, 1, or 2] 0 - Static, 1 - Depth based (Avoids still patterns), 2 - Depth based with random length, noisier but avoids interference patterns.
#endif
#ifndef GI_VARIABLE_MIPLEVELS
	#define GI_VARIABLE_MIPLEVELS 		0	//[0 or 1] 0 - Manual miplevel set in the shader config. 1 - Automatic miplevels.
#endif
#include "ReShade.fxh"

//===================================================================================================================
uniform float GI_DIFFUSE_RADIUS <
	ui_label = "GI - Radius";
	ui_type = "slider";
	ui_min = 0.0; ui_max = 200.0;
	> = 1.0;
uniform float GI_DIFFUSE_STRENGTH <
	ui_label = "GI - Diffuse - Strength";
	ui_type = "slider";
	ui_min = 0.0; ui_max = 30.0;
	> = 4.0;
#if (GI_VARIABLE_MIPLEVELS == 0)	
uniform int GI_DIFFUSE_MIPLEVEL <
	ui_label = "GI - Diffuse - Miplevel";
	ui_type = "slider";
	ui_min = 0; ui_max = GI_DEPTH_MIPLEVELS;
	> = 4;
#endif
uniform int GI_DIFFUSE_CURVE_MODE <
	ui_label = "GI - Diffuse - Curve Mode";
	ui_type = "combo";
	ui_items = "Linear\0Squared\0Log\0Sine\0Mid Range Sine\0";
	> = 4;
uniform int GI_DIFFUSE_BLEND_MODE <
	ui_label = "GI - Diffuse - Blend Mode";
	ui_type = "combo";
	ui_items = "Linear\0Screen\0Soft Light\0Color Dodge\0Hybrid\0";
	> = 2;
uniform float GI_REFLECT_RADIUS <
	ui_label = "GI - Reflection - Radius";
	ui_type = "slider";
	ui_min = 0.0; ui_max = 200.0;
	> = 1.0;
uniform int GI_DIFFUSE_DEBUG <
	ui_label = "GI - Debug";
	ui_type = "combo";
	ui_items = "None\0Color\0Gatherer\0";
	> = 0;
uniform int GI_FOV <
	ui_label = "FoV";
	ui_type = "slider";
	ui_min = 10; ui_max = 90;
	> = 75;

//===================================================================================================================
texture2D	TexGINormalDepth {Width = BUFFER_WIDTH * GI_DEPTH_TEXTURE_QUALITY; Height = BUFFER_HEIGHT * GI_DEPTH_TEXTURE_QUALITY; Format = RGBA16; MipLevels = GI_DEPTH_MIPLEVELS;};
sampler2D	SamplerGIND {Texture = TexGINormalDepth; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; AddressU = Clamp; AddressV = Clamp;};

texture2D	TexGI {Width = BUFFER_WIDTH * GI_TEXTURE_QUALITY; Height = BUFFER_HEIGHT * GI_TEXTURE_QUALITY; Format = RGBA8;};
sampler2D	SamplerGI {Texture = TexGI; MinFilter = LINEAR; MagFilter = LINEAR; MipFilter = LINEAR; AddressU = Clamp; AddressV = Clamp;};
//===================================================================================================================
float GetRandom(float2 co)
{
	// From http://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl
	return frac(sin(dot(co, float2(12.9898, 78.233))) * 43758.5453);
}

float2 GetRandomVector(float2 coords)
{
	return normalize(float2(GetRandom(coords)*2-1, GetRandom(1.42 * coords)*2-1));
}

float2 Rotate45(float2 coords) {
	#define sincos45 0.70710678118
	float x = coords.x * sincos45;
	float y = coords.y * sincos45;
	return float2(x - y, x + y);
}

float2 Rotate90(float2 coords)
{
	return float2(-coords.y, coords.x);
}

float3 EyeVector(float3 vec)
{
	vec.xy = vec.xy * 2.0 - 1.0;
	vec.x -= vec.x * (1.0 - vec.z) * sin(radians(GI_FOV));
	vec.y -= vec.y * (1.0 - vec.z) * sin(radians(GI_FOV * (ReShade::PixelSize.y / ReShade::PixelSize.x)));
	return vec;
}

float3 BlendScreen(float3 a, float3 b)
{
	return 1 - ((1 - a) * (1 - b));
}

float3 BlendSoftLight(float3 a, float3 b)
{
	return (1 - 2 * b) * pow(a, 2) + 2 * b * a;
}

float3 BlendColorDodge(float3 a, float3 b)
{
	return a / (1 - b);
}

//===================================================================================================================
float4 PS_DepthPrePass(float4 vpos : SV_Position, float2 texcoord : TEXCOORD) : COLOR
{
	const float2 offsety = float2(0.0, ReShade::PixelSize.y);
  	const float2 offsetx = float2(ReShade::PixelSize.x, 0.0);
	
	float pointdepth = saturate(ReShade::GetLinearizedDepth(texcoord));
	#define NORMAL_MODE 2
	
	#if (NORMAL_MODE == 0) 
	const float3 p = EyeVector(float3(texcoord, pointdepth));
	float3 py1 = EyeVector(float3(texcoord + offsety, saturate(ReShade::GetLinearizedDepth(texcoord + offsety)))) - p;
  	float3 px1 = EyeVector(float3(texcoord + offsetx, saturate(ReShade::GetLinearizedDepth(texcoord + offsetx)))) - p;
	#elif (NORMAL_MODE == 1) 
	float3 py1 = EyeVector(float3(texcoord + offsety, saturate(ReShade::GetLinearizedDepth(texcoord + offsety)))) - EyeVector(float3(texcoord - offsety, GetDepth(texcoord - offsety)));
  	float3 px1 = EyeVector(float3(texcoord + offsetx, saturate(ReShade::GetLinearizedDepth(texcoord + offsetx)))) - EyeVector(float3(texcoord - offsetx, GetDepth(texcoord - offsetx)));
	#elif (NORMAL_MODE == 2)
	const float3 p = EyeVector(float3(texcoord, pointdepth));
	float3 py1 = EyeVector(float3(texcoord + offsety, saturate(ReShade::GetLinearizedDepth(texcoord + offsety)))) - p;
	const float3 py2 = p - EyeVector(float3(texcoord - offsety, saturate(ReShade::GetLinearizedDepth(texcoord - offsety))));
  	float3 px1 = EyeVector(float3(texcoord + offsetx, saturate(ReShade::GetLinearizedDepth(texcoord + offsetx)))) - p;
	const float3 px2 = p - EyeVector(float3(texcoord - offsetx, saturate(ReShade::GetLinearizedDepth(texcoord - offsetx))));
	py1 = lerp(py1, py2, abs(py1.z) > abs(py2.z));
	px1 = lerp(px1, px2, abs(px1.z) > abs(px2.z));
	#endif
  
  	return float4((normalize(cross(py1, px1)) + 1.0) * 0.5, pointdepth);
}

float4 PS_GIDiffuse(float4 vpos : SV_Position, float2 texcoord : TEXCOORD) : COLOR
{
	float4 res;
	float4 pointnd = tex2D(SamplerGIND, texcoord);
	if (pointnd.w == 1.0) return 0.0;
	pointnd.xyz = (pointnd.xyz * 2.0) - 1.0;

	const float3 pointvector = EyeVector(float3(texcoord, pointnd.w));

	#if (GI_VECTOR_MODE == 0)
	float2 randomvector = GetRandomVector(texcoord);
	#elif (GI_VECTOR_MODE == 1)
	float2 randomvector = GetRandomVector(texcoord * pointnd.w);
	#elif (GI_VECTOR_MODE == 2)
	float2 randomvector = GetRandomVector(texcoord * pointnd.w) * (0.5 + GetRandom(texcoord)/2);
	#endif

	float2 psize = lerp(GI_DIFFUSE_RADIUS, 1.0, pow(abs(pointnd.w), 0.25)) * ReShade::PixelSize;
	psize /= GI_TEXTURE_QUALITY;

	for(int p=1; p <= GI_DIFFUSE_PASSES; p++)
	{
		float2 coordmult = psize * p;
		#if GI_VARIABLE_MIPLEVELS
		int miplevel = round(smoothstep(1, GI_DIFFUSE_PASSES, p) * GI_DEPTH_MIPLEVELS);
		#endif
		
		for(int i=0; i < 4; i++)
		{
			randomvector = Rotate90(randomvector);
			float2 tapcoord = texcoord + randomvector * coordmult;
			#if GI_VARIABLE_MIPLEVELS
			float4 tapnd = tex2Dlod(SamplerGIND, float4(tapcoord, 0, miplevel));
			#else
			float4 tapnd = tex2Dlod(SamplerGIND, float4(tapcoord, 0, GI_DIFFUSE_MIPLEVEL));
			#endif
			tapnd.xyz = (tapnd.xyz * 2.0) - 1.0;
			if (tapnd.w == 1.0) continue;
			float3 tapvector = EyeVector(float3(tapcoord, tapnd.w));

			float3 pttvector = tapvector - pointvector;
				
			float weight = (1.0 - max(0.0, dot(pointnd.xyz, tapnd.xyz))); // How much normals are facing each other
			weight *= max(0.0, -dot(normalize(pttvector), tapnd.xyz)); // How much the normal is facing the center point
			weight *= saturate(coordmult.x - abs(pttvector.z)) / coordmult.x; // Z distance
			float3 coltap = tex2Dlod(ReShade::BackBuffer, float4(tapcoord, 0, 0)).rgb;
			res.rgb += coltap * weight;
			res.w += weight;
		}
		randomvector = Rotate45(randomvector);
	}

	res /= 4 * GI_DIFFUSE_PASSES;
	
	return res;
}

#define threepitwo 4.71238898038f
#define pi 3.14159265358
float4 PS_GICombine(float4 vpos : SV_Position, float2 texcoord : TEXCOORD) : COLOR
{
	float4 diffuse = tex2D(SamplerGI, texcoord);
	float4 res = tex2D(ReShade::BackBuffer, texcoord);

	//if (GI_DIFFUSE_CURVE_MODE == 0) // Linear
		//Do Nothing
	if (GI_DIFFUSE_CURVE_MODE == 1) // Squared
		diffuse = pow(diffuse, 2);
	else if (GI_DIFFUSE_CURVE_MODE == 2) // Logarithm
		diffuse = log10(diffuse * 10.0);
	else if (GI_DIFFUSE_CURVE_MODE == 3) // Sine
		diffuse = (sin(threepitwo + diffuse * pi) + 1) / 2;
	else if (GI_DIFFUSE_CURVE_MODE == 4) // Mid range Sine
		diffuse = sin(diffuse * pi);
	diffuse = saturate(diffuse * GI_DIFFUSE_STRENGTH);

	if (GI_DIFFUSE_BLEND_MODE == 0) // Linear
		res.rgb += diffuse.rgb;
	else if (GI_DIFFUSE_BLEND_MODE == 1) // Screen
		res.rgb = BlendScreen(res.rgb, diffuse.rgb);
	else if (GI_DIFFUSE_BLEND_MODE == 2) // Soft Light
		res.rgb = BlendSoftLight(res.rgb, 0.5 + diffuse.rgb);
	else if (GI_DIFFUSE_BLEND_MODE == 3) // Color Dodge
		res.rgb = BlendColorDodge(res.rgb, diffuse.rgb);
	else // Hybrid based on point brightness
		res.rgb = lerp(res.rgb + diffuse.rgb, res.rgb * (1.0 + diffuse.rgb), dot(res.rgb, 0.3333));
	

	if (GI_DIFFUSE_DEBUG == 1)
		res.rgb = diffuse.rgb;
	else if (GI_DIFFUSE_DEBUG == 2)
		res.rgb = diffuse.w;

	return float4(res.rgb, 1.0);
}
//===================================================================================================================
technique Pirate_GI
{
	pass DepthPre
	{
		VertexShader = PostProcessVS;
		PixelShader  = PS_DepthPrePass;
		RenderTarget = TexGINormalDepth;
	}
	pass Diffuse
	{
		VertexShader = PostProcessVS;
		PixelShader  = PS_GIDiffuse;
		RenderTarget = TexGI;
	}
	pass GITest
	{
		VertexShader = PostProcessVS;
		PixelShader  = PS_GICombine;
	}
}